<?php

/*
 * Copyright (C) 2009 - 2011 Pham Cong Dinh
 *
 * This file is part of Spica.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 3 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

/**
 * <p>Set of methods considering manipulations of date and time string.</p>
 *
 * @category   spica
 * @package    core
 * @package    utils
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.2
 * @since      March 22, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: DateUtils.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
class SpicaDateUtils
{
    /**
     * Figures out what fiscal year a specified date is in.
     *
     * @example
     *   // my fiscal year starts on July,1 and ends on June 30, so...
     *   echo SpicaDateUtils::calculateFiscalYearForDate("5/15/08", "7/1", "6/30");
     *   // returns 2008
     *   echo SpicaDateUtils::calculateFiscalYearForDate("12/1/08", "7/1", "6/30");
     *
     * @author lamb dot dan at gmail dot com
     * @param  string $inputDate - the date you wish to find the fiscal year for. (12/4/08)
     * @param  string $fyStartDate - the month and day your fiscal year starts. (7/1)
     * @param  string $fyEndDate - the month and day your fiscal year ends. (6/30)
     * @return string returns the correct fiscal year
     */
    public static function calculateFiscalYearForDate($inputDate, $fyStartDate, $fyEnd)
    {
        $date        = strtotime($inputDate);
        $inputYear   = strftime('%Y', $date);
        $fyStartDate = strtotime($fyStartDate.$inputYear);
        $fyEndDate   = strtotime($fyEndDate.$inputYear);

        if ($date < $fyEndDate)
        {
            return intval($inputYear);
        }

        return (int) ((int)($inputYear) + 1);
    }

    /**
     * <p>Formats a date time string, using a human readable string
     * representation of dates</p>
     *
     * <p>Supported format:</p>
     * <pre>
     * + Day:
     *   DDDD: A full textual representation of the day: Sunday through Saturday
     *   DDD:  An abbreviated textual representation of the day: Sun through Sat
     *   DD:   Two-digit day of the month (with leading zeros): 00 - 31
     *   dd:   Day of the month, with a space preceeding single digits: ' 1', ' 2'
     * + Month:
     *   MMMM: Full month name, based on the locale: January through December
     *   MMM: Abbreviated month name, based on the locale: Jan through Dec
     *   MM: Two digit representation of the month: 01 (for January) through 12 (for December)
     * + Year:
     *   YYYY: Four digit representation for the year
     *   YY: Two digit representation of the year
     * + Hour:
     *   HH: Two digit representation of the hour in 24-hour format: 00 through 23
     *   hh: Two digit representation of the hour in 12-hour format: 01 through 12
     * + Minutes:
     *   mm: Two digit representation of the minute: 00 through 59
     * + Seconds
     *   ss: Two digit representation of the second: 00 through 59
     * + AM/PM: Meridian indicator
     *   AM: UPPER-CASE 'AM' or 'PM' based on the given time
     *   am: lower-case 'am' or 'pm' based on the given time
     * </pre>
     * @example
     * + DD MM YYYY    => 01 07 1978
     * + DDD dD MMM YY => Mon 1 Jul 78
     *
     * @param  string $targetFormat
     * @param  string $dateTimeInput must be a valid US English date format
     * @return string|bool A formatted date string if success or false otherwise
     */
    public static function format($targetFormat, $dateTimeInput = null)
    {
        if (null === $dateTimeInput)
        {
            $dateTimeInput = time();
        }

        $timestamp = strtotime($dateTimeInput);

        if (false  === $timestamp)
        {
            return false;
        }

        $format = self::getNativeFormat($targetFormat);
        return strftime($format, strtotime($dateTimeInput));
    }

    /**
     * <p>Converts a formatted date time string to another format,
     * using human readable string representation of dates.</p>
     *
     * <p>Supported format:</p>
     * <pre>
     * + Day:
     *   DDDD: A full textual representation of the day: Sunday through Saturday
     *   DDD:  An abbreviated textual representation of the day: Sun through Sat
     *   DD:   Two-digit day of the month (with leading zeros): 00 - 31
     *   dd:   Day of the month, with a space preceeding single digits: ' 1', ' 2'
     * + Month:
     *   MMMM: Full month name, based on the locale: January through December
     *   MMM: Abbreviated month name, based on the locale: Jan through Dec
     *   MM: Two digit representation of the month: 01 (for January) through 12 (for December)
     * + Year:
     *   YYYY: Four digit representation for the year
     *   YY: Two digit representation of the year
     * + Hour:
     *   HH: Two digit representation of the hour in 24-hour format: 00 through 23
     *   hh: Two digit representation of the hour in 12-hour format: 01 through 12
     * + Minutes:
     *   mm: Two digit representation of the minute: 00 through 59
     * + Seconds
     *   ss: Two digit representation of the second: 00 through 59
     * + AM/PM: Meridian indicator
     *   AM: UPPER-CASE 'AM' or 'PM' based on the given time
     *   am: lower-case 'am' or 'pm' based on the given time
     * </pre>
     * @example
     * + DD MM YYYY    => 01 07 1978
     * + DDD dD MMM YY => Mon 1 Jul 78
     *
     * @param  string $dateTimeInput must be a valid US English date format
     * @param  string $targetFormat
     * @param  string $originalFormat
     * @return string|bool A formatted date string if success or false otherwise
     */
    public function changeFormat($dateTimeInput, $targetFormat, $originalFormat)
    {
        if (null === $dateTimeInput)
        {
            throw new InvalidArgumentException('A date time string to convert must be provided. ');
        }
    }

    /**
     * <p>Formats a date time string, using a human readable string
     * representation of dates.</p>
     *
     * <p>Supported format:</p>
     * <pre>
     * + Day:
     *   DDDD: A full textual representation of the day: Sunday through Saturday
     *   DDD:  An abbreviated textual representation of the day: Sun through Sat
     *   DD:   Two-digit day of the month (with leading zeros): 00 - 31
     *   dd:   Day of the month, with a space preceeding single digits: ' 1', ' 2'
     * + Month:
     *   MMMM: Full month name, based on the locale: January through December
     *   MMM: Abbreviated month name, based on the locale: Jan through Dec
     *   MM: Two digit representation of the month: 01 (for January) through 12 (for December)
     * + Year:
     *   YYYY: Four digit representation for the year
     *   YY: Two digit representation of the year
     * + Hour:
     *   HH: Two digit representation of the hour in 24-hour format: 00 through 23
     *   hh: Two digit representation of the hour in 12-hour format: 01 through 12
     * + Minutes:
     *   mm: Two digit representation of the minute: 00 through 59
     * + Seconds
     *   ss: Two digit representation of the second: 00 through 59
     * + AM/PM: Meridian indicator
     *   AM: UPPER-CASE 'AM' or 'PM' based on the given time
     *   am: lower-case 'am' or 'pm' based on the given time
     * </pre>
     *
     * @param string $humanReadableFormat
     */
    public static function getNativeFormat($humanReadableFormat)
    {
        $map = self::_getFormatMap();
        // Binary-safe and case-sensitive
        return str_replace(array_keys($map), array_values($map), $format);
    }

    /**
     * Gets the pattern mapping of human format indicators.
     *
     * @see    http://regexlib.com/DisplayPatterns.aspx?cattabindex=4&categoryid=5&p=5
     * @return array
     */
    protected static function _getFormatPatternMap()
    {
        return $map = array(
          'DDDD' => "(Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday)",
          'DDD'  => "(Sun|Mon|Tue|Wed|Thu|Fri|Sat)",
          'DD'   => '(\d{2})',
          'dd'   => "( [1-12]{1})",
          'MMMM' => '(January|February|March|April|May|June|July|August|September|October|November|December)',
          'MMM'  => '(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)',
          'MM'   => "(\d{2})",
          'YYYY' => "(\d{4})",
          'YY'   => "(\d{2})",
          'HH'   => "([0-2]{1}[0-3]{1})",
          'hh'   => "([0-1]{1}[0-2]{1})",
          'mm'   => "(\d{2})",
          'ss'   => "(\d{2})",
          'AM'   => "([AM|PM]{1})",
          'am'   => "([am|pm]{1})",
        );
    }

    /**
     * Gets the mapping of human format indicators and native PHP format ones.
     *
     * @return array
     */
    protected static function _getFormatPattern()
    {
        return $map = array(
          'DDDD' => "%A",
          'DDD'  => "%a",
          'DD'   => "%d",
          'dd'   => "%e",
          'MMMM' => "%B",
          'MMM'  => "%b",
          'MM'   => "%m",
          'YYYY' => "%Y",
          'YY'   => "%y",
          'HH'   => "%H",
          'hh'   => "%I",
          'mm'   => "%M",
          'ss'   => "%S",
          'AM'   => "%p",
          'am'   => "%P",
        );
    }

    /**
     * Parses a string using the given format to get a PHP-compatible time string.
     * FIXME Not completed
     *
     * @param  string $value  date string
     * @param  string $format format pattern that is defined in SpicaDatePattern
     * @return DateTime|false
     */
    public static function getDateFromFormat($value, $format)
    {
        if (!preg_match($format, $value, $matches))
        {
            return false;
        }

        $year   = '';
        $month  = '';
        $day    = '';
        $hour   = 00;
        $minute = 00;
        $second = 00;

        if (isset($matches['year']))
        {
            $year = $matches['year'];
        }

        if (isset($matches['month']))
        {
            $month = spica_date_month2number($matches['month']);
        }

        if (isset($matches['day']))
        {
            $day = spica_date_day2number($matches['day']);
        }

        if (isset($matches['hh12']))
        {
            $hour = $matches['hh12'];
        }

        if (isset($matches['hh24']))
        {
            $hour = $matches['hh24'];
        }

        if (isset($matches['mi']))
        {
            $minute2 = $matches['mi'];
        }

        if (isset($matches['ss']))
        {
            $second = $matches['ss'];
        }

        if (isset($matches['am_pm']))
        {
            $second = $matches['am_pm'];
        }
    }

    /**
     * Gets day name of week, 0 = Sunday,... 6 = Saturday.
     *
     * Modified from PEAR::Date_Calc
     *
     * @param  int $year
     * @param  int $month
     * @param  int $day
     * @return int
     */
    public function getDayOfWeek($year, $month, $day)
    {
        /*
         Pope Gregory removed 10 days - October 5 to October 14 - from the year 1582 and
         proclaimed that from that time onwards 3 days would be dropped from the calendar
         every 400 years.

         Thursday, October 4, 1582 (Julian) was followed immediately by Friday, October 15, 1582 (Gregorian).
         */
        if ($year <= 1582)
        {
            if ($year < 1582 || ($year == 1582 && ($month < 10 || ($month == 10 && $day < 15))))
            {
                $popeCorrection = 3;
            }
            else
            {
                $popeCorrection = 0;
            }
        }
        else
        {
            $popeCorrection = 0;
        }

        if ($month > 2)
        {
            $month -= 2;
        }
        else
        {
            $month += 10;
            $year--;
        }

        $day =  floor((13 * $month - 1) / 5) +
        $day + ($year % 100) +
        floor(($year % 100) / 4) +
        floor(($year / 100) / 4) - 2 *
        floor($year / 100) + 77 + $popeCorrection;

        return $day - 7 * floor($day / 7);
    }

    public static function readSeconds($secs)
    {
        if ($secs >= 86400)
        {
            $days = floor($secs/86400);
            $secs = $secs%86400;
            $r    = $days.' day';

            if ($days <> 1)
            {
                $r.='s';
            }

            if ($secs > 0)
            {
                $r.=', ';
            }
        }
        elseif ($secs >= 3600)
        {
            $hours = floor($secs/3600);
            $secs  = $secs%3600;
            $r    .= $hours.' hour';

            if ($hours <> 1)
            {
                $r.='s';
            }

            if ($secs > 0)
            {
                $r.=', ';
            }
        }
        elseif ($secs >= 60)
        {
            $minutes = floor($secs/60);
            $secs    = $secs%60;
            $r      .= $minutes.' minute';

            if ($minutes <> 1)
            {
                $r.='s';
            }

            if ($secs > 0)
            {
                $r.=', ';
            }
        }

        $r .= $secs.' second';

        if ($secs <> 1)
        {
            $r .= 's';
        }

        return $r;
    }
}

/**
 * Calculates the date difference between 2 dates.
 *
 * @param  string $start  Start date string
 * @param  string $end    End date string
 * @param  string $format Regex pattern defined in SpicaDatePattern
 * @return int Negative integer returns if $start is after $end
 */
function spica_date_diff($start, $end, $format)
{
    if (!preg_match($format, $start, $match1))
    {
        throw new InvalidArgumentException('Argument $start is not valid for specified format: '.$format);
    }

    if (!preg_match($format, $end, $match2))
    {
        throw new InvalidArgumentException('Argument $start is not valid for specified format: '.$format);
    }

    if (isset($match2['month']))
    {
        $match2['mm'] = spica_date_month2number($match2['month']);
    }
    elseif (isset($match2['mon']))
    {
        $match2['mm'] = spica_date_month2number($match2['mon']);
    }

    if (isset($match1['month']))
    {
        $match1['mm'] = spica_date_month2number($match1['month']);
    }
    elseif (isset($match1['mon']))
    {
        $match1['mm'] = spica_date_month2number($match1['mon']);
    }

    // Calculate date difference
    $diff = gregoriantojd($match2['mm'], $match2['dd'], $match2['yyyy']) - gregoriantojd($match1['mm'], $match1['dd'], $match1['yyyy']);
    return ($diff < 0) ? -$diff : $diff;
}

/**
 * Gets list of textual representation of month names in a year.
 * Indexed using ISO-8601 numeric representation of the day of the week: 1 (for January) through 12 (for December)
 *
 * @param  bool $full return full name for month
 * @return array
 */
function spica_date_get_months($full = true)
{
    if (true === $full)
    {
        return array(
        1  => 'January',
        2  => 'February',
        3  => 'March',
        4  => 'April',
        5  => 'May',
        6  => 'June',
        7  => 'July',
        8  => 'August',
        9  => 'September',
        10 => 'October',
        11 => 'November',
        12 => 'December');
    }

    return array(
    1  => 'Jan',
    2  => 'Feb',
    3  => 'Mar',
    4  => 'Apr',
    5  => 'May',
    6  => 'Jun',
    7  => 'Jul',
    8  => 'Aug',
    9  => 'Sep',
    10 => 'Oct',
    11 => 'Nov',
    12 => 'Dec');
}

/**
 * Gets list of textual representation of the day of the week.
 * Indexed using ISO-8601 numeric representation of the day of the week: 1 (for Monday) through 7 (for Sunday)
 *
 * @param  bool $full return full name for day
 * @return array
 */
function spica_date_get_weekdays($full = true)
{
    if (true === $full)
    {
        return array(
        1 => 'Monday',
        2 => 'Tuesday',
        3 => 'Wednesday',
        4 => 'Thursday',
        5 => 'Friday',
        6 => 'Saturday',
        7 => 'Sunday',
        );
    }

    return array(
    1 => 'Mon',
    2 => 'Tue',
    3 => 'Wed',
    4 => 'Thur',
    5 => 'Fri',
    6 => 'Sat',
    7 => 'Sun',
    );
}

/**
 * Gets textual representation of the day of the week.
 *
 * @param  string|int $number ISO-8601 numeric representation of the day of the week.
 *                    1 (for Monday) through 7 (for Sunday)
 * @param  bool       $full return full name for day
 * @return string|false
 */
function spica_date_num2day($number, $full = true)
{
    $days = spica_date_get_weekdays($full);
    return isset($days[$number]) ? $days[$number] : false;
}

/**
 * Gets textual representation of the day of the week.
 *
 * @param  string $weekDay Week day name
 * @return int|false
 */
function spica_date_day2number($weekDay)
{
    $weekDay = ucfirst(strtolower($weekDay));
    $days    = array_flip(spica_date_get_weekdays());
    if (true === isset($days[$weekDay]))
    {
        return $days[$weekDay];
    }

    $days = array_flip(spica_date_get_weekdays(false));
    return isset($days[$weekDay]) ? $days[$weekDay] : false;
}

/**
 * Gets textual representation of the month of a year.
 *
 * @param  string|int $number ISO-8601 numeric representation of the month of a year.
 *                    1 (for January) through 12 (for December)
 * @param  bool       $full return full name for the month
 * @return string|false
 */
function spica_date_num2month($number, $full = true)
{
    $months = spica_date_get_months($full);
    return isset($months[$number]) ? $months[$number] : false;
}

/**
 * Gets numeric representation of the month of a year.
 *
 * @param  string $month Month name
 * @return int|false
 */
function spica_date_month2number($month)
{
    $month   = ucfirst(strtolower($month));
    $months  = array_flip(spica_date_get_months());
    if (true === isset($months[$month]))
    {
        return $months[$month];
    }

    $months = array_flip(spica_date_get_months(false));
    return isset($months[$month]) ? $months[$month] : false;
}

/**
 * Reads time difference in human language.
 *
 * @param  int $timestamp
 * @param  int $granularity
 * @param  string $format
 * @return string
 */
function spica_date_read_timeago($timestamp, $granularity = 2, $format = 'Y-m-d H:i:s')
{
    $difference = time() - $timestamp;

    if ($difference < 60)
    {
        return (($difference < 0) ? 0 : $difference).' seconds ago';
    }

    if ($difference < 864000) // if difference is over 10 days show normal time form
    {
        $periods = array(
          'week' => 604800,
          'day'  => 86400,
          'hr'   => 3600,
          'min'  => 60,
          'sec'  => 1,
        );

        $output  = '';

        foreach ($periods as $key => $value)
        {
            if ($difference >= $value)
            {
                $time        = round($difference / $value);
                $difference %= $value;
                $output     .= ($output ? ' ' : '').$time.' ';
                $output     .= (($time > 1 && $key == 'day') ? $key.'s' : $key);
                $granularity--;
            }

            if ($granularity == 0)
            {
                break;
            }
        }

        return ($output ? $output : '0 seconds').' ago';
    }

    return date($format, $timestamp);
}

/**
 * Gets range of months between 2 dates.
 *
 * $startDate = 'October 30, 1986';
 * $endDate = 'June 1, 1987';
 * $months = spica_date_get_month_range($startDate, $endDate);
 *
 * @param  string $startDate
 * @param  string $endDate
 * @return array
 */
function spica_date_get_month_range($startDate, $endDate)
{
    if (false === is_int($startTimestamp))
	{
        $startTimestamp = strtotime($startDate);
        if (false === $startTimestamp)
		{
            throw new Exception("Invalid value provided for the start date!");
		}
    }

    if (false === is_int($endTimestamp))
	{
        $endTimestamp = strtotime($endDate);
        if (false === $endTimestamp)
		{
            throw new Exception("Invalid value provided for the end date!");
        }
    }

    if ($startTimestamp > $endTimestamp)
	{
        throw new Exception("The start time cannot be greater than the end time!");
    }

    $startMonth = (int) date('n', $startTimestamp);
    $startYear  = (int) date('Y', $startTimestamp);
    $startMonthTimestamp = mktime(null, null, null, $startMonth, 1, $startYear);

    $endMonth = (int)date('n', $endTimestamp);
    $endYear  = (int)date('Y', $endTimestamp);
    $endMonthTimestamp = mktime(null, null, null, $endMonth, 1, $endYear);

    $months = array();
    $currentMonthTimestamp = $startMonthTimestamp;
    while ($currentMonthTimestamp <= $endMonthTimestamp)
	{
        // This is the value that gets added to the returned array.
        // It can be whatever you want -- string value, another array, etc.
        // At this point in the function execution, $currentMonthTimestamp
        // is a timestamp for the first day of the current month in the loop,
        // and that's what you'll want to use to generate the return value.
        $months[] = date('F Y', $currentMonthTimestamp);

        $currentMonth = (int) date('n', $currentMonthTimestamp);
        $currentYear  = (int) date('Y', $currentMonthTimestamp);
        if ($currentMonth++ > 12)
		{
            $currentMonth = 1;
            $currentYear++;
        }

        $currentMonthTimestamp = mktime(null, null, null, $currentMonth, 1, $currentYear);
    }

    return $months;
}

/**
 * <p>Set of constants used in date utilities.</p>
 *
 * @category   spica
 * @package    core
 * @package    utils
 * @author     Pham Cong Dinh <pcdinh at phpvietnam dot net>
 * @since      Version 0.2
 * @since      March 22, 2009
 * @copyright  Pham Cong Dinh (http://www.phpvietnam.net)
 * @license    http://www.gnu.org/licenses/lgpl-3.0.txt
 * @version    $Id: DateUtils.php 1869 2011-01-07 18:55:25Z pcdinh $
 */
interface SpicaDatePattern
{
    /**
     * Represents date format: YYYYMMDD like 20090809 (no separator)
     *
     * @var string
     */
    const YYYYMMDD = '#^(?P<yyyy>\d{4})(?P<mm>\d{2})(?P<dd>\d{2})$#';

    /**
     * Represents date format: YYYYMMDD HH12:MI:SS like 20090809 09:04:37
     * (no separator for date but colons are used for time)
     *
     * @var string
     */
    const YYYYMMDD_HH12MISS_C = '#^(?P<yyyy>\d{4})(?P<mm>\d{2})(?P<dd>\d{2}) (?P<hh12>\d{2}):(?P<mi>\d{2}):(?P<s>\d{2})$#';

    /**
     * Represents date format: YYYYMMDD HH24:MI:SS like 20090809 18:04:37
     * (no separator for date but colons are used for time)
     *
     * @var string
     */
    const YYYYMMDD_HH24MISS_C = '#^(?P<yyyy>\d{4})(?P<mm>\d{2})(?P<dd>\d{2}) (?P<hh24>\d{2}):(?P<mi>\d{2}):(?P<s>\d{2})$#';

    /**
     * Represents date format: YYYY/MM/DD like 2009/08/09 (forward slash as separator)
     *
     * @var string
     */
    const YYYY_MM_DD_S = '^#(?P<yyyy>\d{4})/(?P<mm>\d{2}/(?P<dd>\d{2})$#';

    /**
     * Represents date format: YYYY/MM/DD HH12:MI:SS like 2009/08/09 12:04:37
     * (forward slash as date separator, colon as time separator)
     *
     * @var string
     */
    const YYYY_MM_DD_S_HH12MISS_C = '^#(?P<yyyy>\d{4})/(?P<mm>\d{2}/(?P<dd>\d{2}) (?P<h12>\d{2}):(?P<mi>\d{2}):(?P<s>\d{2})$#';

    /**
     * Represents date format: YYYY.MM.DD like 2009.08.09 (dot as date separator)
     *
     * @var string
     */
    const YYYY_MM_DD_DOT = '#^(?P<yyyy>\d{4})\.(?P<mm>\d{2})\.(?P<dd>\d{2}))$#';

    /**
     * Represents date format: YYYY.MM.DD HH12:MI:SS like 2009.08.09 12:04:37 (dot as date separator)
     *
     * @var string
     */
    const YYYY_MM_DD_DOT_HH12MISS_C = '#^(?P<yyyy>\d{4})\.(?P<mm>\d{2})\.(?P<dd>\d{2}) (?P<h12>\d{2}):(?P<mi>\d{2}):(?P<s>\d{2})$#';

    /**
     * Represents date format: YYYY-MM-DD like 2009-08-09 (dash as separator)
     *
     * @var string
     */
    const YYYY_MM_DD_DASH = '#^(?P<yyyy>\d{4})\-(?P<mm>\d{2})\-(?P<dd>\d{2})$#';

    /**
     * Represents date format: YYYYMMMMDD like 2009January09 (no separator)
     *
     * @var string
     */
    const YYYYMMMMDD = '#^(?P<yyyy>\d{4})(?P<month>January|February|March|April|May|June|July|August|September|October|November|December)(?P<dd>\d{2})$#i';

    /**
     * Represents date format: YYYY MMMM DD like 2009 January 09
     *
     * @var string
     */
    const YYYY_MMMM_DD = '#^(?P<yyyy>\d{4}) (?P<month>January|February|March|April|May|June|July|August|September|October|November|December) (?P<dd>\d{2})$#i';

    /**
     * Represents date format: MMMM DD YYYY like January 09 2009
     *
     * @var string
     */
    const MMMM_DD_YYYY = '#^(?P<month>January|February|March|April|May|June|July|August|September|October|November|December) (?P<dd>\d{2}) (?P<yyyy>\d{4})$#i';

    /**
     * Represents US date format: MMMM DD, YYYY like January 09, 2009
     *
     * @var string
     */
    const MMMM_DD_YYYY_US = '#^(?P<month>January|February|March|April|May|June|July|August|September|October|November|December) (?P<dd>\d{2}), (?P<yyyy>\d{4})$#i';

    /**
     * Represents date format: YYYY MMM DD like 2009 Jan 09 (no separator)
     *
     * @var string
     */
    const YYYY_MON_DD = '#^(?P<yyyy>\d{4}) (?P<mon>Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) (?P<dd>\d{2})$#i';
}

?>